home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / WAIS / next-ui / WaisQuestion.m < prev    next >
Encoding:
Text File  |  1992-03-18  |  14.2 KB  |  534 lines

  1. // WaisQuestion.m
  2. //
  3. // Free software created 1 Feb 1992
  4. // by Paul Burchard <burchard@math.utah.edu>.
  5. // Incorporating:
  6. /* 
  7.    WIDE AREA INFORMATION SERVER SOFTWARE:
  8.    No guarantees or restrictions.  See the readme file for the full standard
  9.    disclaimer.
  10.  
  11.    This is part of the [NeXTstep] user-interface for the WAIS software.
  12.    Do with it as you please.
  13.  
  14.    Version 0.82
  15.    Wed Apr 24 1991
  16.  
  17.    jonathan@Think.COM
  18.  
  19. */
  20. //
  21.  
  22. #import "WaisQuestion.h"
  23.  
  24. // Search path for questions.
  25. static id questionFolderList;
  26.  
  27. // Maximum number of search results (unless instance's searchLimit is higher).
  28. static int globalSearchLimit = SEARCH_LIMIT_DEFAULT;
  29.  
  30. // Error panel title.
  31. static char *errorTitle = "WAIS Question Error!";
  32.  
  33. // Decoders for structured WAIS files.
  34.  
  35. _WaisDecoder waisRectDecoder[] = 
  36. {
  37.     { ":left",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  38.     { ":right",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  39.     { ":top",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  40.     { ":bottom",    W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  41.     { NULL }
  42. };
  43.  
  44. _WaisDecoder waisQuestionDecoder[] = 
  45. {
  46.     { ":version",    W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  47.     { ":seed-words",    W_FIELD,0,0,    ReadString,3,    WriteString,2,
  48.                         MAX_SYMBOL_SIZE },
  49.     { ":relevant-documents",W_LIST,
  50.         ":document-id",    waisDocumentIDDecoder },
  51.     { ":sources",    W_LIST,
  52.         ":source-id",    waisSourceIDDecoder },
  53.     { ":result-documents",W_LIST,
  54.         ":document-id",    waisDocumentIDDecoder },
  55.     { ":view",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  56.     { ":window-size",    W_STRUCT,
  57.         ":rect",    waisRectDecoder },
  58.     { NULL }
  59. };
  60.  
  61.  
  62. @implementation WaisQuestion
  63.  
  64. + folderList
  65. {
  66.     return questionFolderList;
  67. }
  68.  
  69. + setFolderList:aList
  70. {
  71.     questionFolderList = aList;
  72.     return self;
  73. }
  74.  
  75. + (const char *)defaultHomeFolder
  76. {
  77.     return "/Library/WAIS/questions";
  78. }
  79.  
  80. + (const char *)fileStructName
  81. {
  82.     return ":question";
  83. }
  84.  
  85. + (WaisDecoder)fileStructDecoder
  86. {
  87.     return waisQuestionDecoder;
  88. }
  89.  
  90. + (const char *)errorTitle
  91. {
  92.     return errorTitle;
  93. }
  94.  
  95. + (BOOL)checkFileName:(const char *)fileName
  96. {
  97.     if(!fileName) return NO;
  98.     if(strlen(fileName) <= strlen(W_Q_EXT)) return NO;
  99.     if(!strstr(fileName, W_Q_EXT)) return NO;
  100.     if(0 != strcmp(W_Q_EXT, strstr(fileName, W_Q_EXT))) return NO;
  101.     return YES;
  102. }
  103.  
  104. - initKey:(const char *)aKey
  105. {
  106.     [super initKey:aKey];
  107.     sourceList = [[List alloc] init];
  108.     relevantList = [[List alloc] init];
  109.     resultList = [[List alloc] init];
  110.     scoreList = [[Storage alloc] initCount:0 elementSize:sizeof(float) description:"f"];
  111.     searchLimit = 1;
  112.     listCounter = 0;
  113.     return self;
  114. }
  115.  
  116. - free
  117. {
  118.     [sourceList free];
  119.     [relevantList free];
  120.     [resultList free];
  121.     [scoreList free];
  122.     return [super free];
  123. }
  124.  
  125. - (float)scoreForDocument:waisDocument
  126. {
  127.     int index;
  128.     float *value;
  129.     
  130.     if(![waisDocument isKindOf:[WaisDocument class]]) return 0.0;
  131.     if((index=[resultList indexOf:waisDocument]) == NX_NOT_IN_LIST) return 0.0;
  132.     if(!(value=(float *)[scoreList elementAt:index])) return 0.0;
  133.     return *value;
  134. }
  135.  
  136. - setScoresFromResults
  137. {
  138.     int r, nresult;
  139.     long max_score, this_score;
  140.     float *score_array;
  141.     const char *score;
  142.  
  143.     // Compute and normalize scores.
  144.     nresult = [resultList count];
  145.     [scoreList setNumSlots:nresult];
  146.     score_array = (float *)[scoreList elementAt:0];
  147.     if(!score_array) return nil;
  148.     max_score = 0;
  149.     for(r=0; r<nresult; r++)
  150.         if((score=[[resultList objectAt:r] valueForStringKey:":score"])
  151.         && max_score<(this_score=atol(score)))
  152.         max_score = this_score;
  153.     if(max_score <= 0)
  154.     {
  155.         [scoreList setNumSlots:0];
  156.     ErrorMsg(errorTitle,
  157.         "Found no documents with positive matching scores.");
  158.     return nil;
  159.     }
  160.     for(r=0; r<nresult; r++)
  161.         if(score=[[resultList objectAt:r] valueForStringKey:":score"])
  162.             score_array[r] = ((float)atol(score))/((float)max_score);
  163.     else score_array[r] = 0.0;
  164.     return self;
  165. }
  166.  
  167. - search
  168. {
  169.     int i, s, d, r, nrelevant, nsource, nretrieve, nresult;
  170.     long request_buffer_length, limit, startat, endat;
  171.     id source, doc;
  172.     const char *database, *theType, *score;
  173.     char buf[MAX_SYMBOL_SIZE];
  174.     static char request[MAX_MESSAGE_LEN], response[MAX_MESSAGE_LEN];
  175.     DocObj **Doco;
  176.     DocID *docid;
  177.     SearchResponseAPDU *interp_response;
  178.     WAISDocumentHeader *header; 
  179.     diagnosticRecord **diag;
  180.  
  181.     // Start results fresh.
  182.     [resultList empty];
  183.     [scoreList setNumSlots:0];
  184.  
  185.     // The DocObj list Doco formats the relevant documents for the WAIS lib.
  186.     Doco = (DocObj**)s_malloc(([[self relevantList] count]+1)*sizeof(char*));
  187.     nrelevant = [[self relevantList] count];
  188.     for(d=0; d<nrelevant; d++)
  189.     {
  190.         doc = [[self relevantList] objectAt:d];
  191.     if((docid=[doc waisDocID]) && docid->originalLocalID)
  192.     {
  193.         theType = [doc valueForStringKey:":type"];
  194.         if(!theType) theType = "TEXT";
  195.         if([doc valueForStringKey:":start"])
  196.             startat = atol([doc valueForStringKey:":start"]);
  197.         else startat = (-1);
  198.         if([doc valueForStringKey:":end"])
  199.             endat = atol([doc valueForStringKey:":end"]);
  200.         else endat = (-1);
  201.         if(startat >= 0) Doco[d] = makeDocObjUsingLines(
  202.         anyFromDocID(docid), theType, startat, endat);
  203.         else Doco[d] = makeDocObjUsingWholeDocument(
  204.             anyFromDocID(docid), theType);
  205.     }
  206.     }
  207.     Doco[d] = NULL;
  208.  
  209.     // Any sources or key words?
  210.     if([[self sourceList] count] <= 0)
  211.         { ErrorMsg(errorTitle, "No sources to search."); return nil; }
  212.     if(![self keywords])
  213.         { ErrorMsg(errorTitle, "No key words for search."); return nil; }
  214.     
  215.     // Apportion search limit equally over the sources.
  216.     nsource = [[self sourceList] count];
  217.     limit = ((searchLimit>globalSearchLimit) ? searchLimit : globalSearchLimit)
  218.         / nsource;
  219.  
  220.     // Loop over sources.
  221.     for(s=0; s<nsource; s++)
  222.     {
  223.     // Make sure source is valid and connected.
  224.         source = [[self sourceList] objectAt:s];
  225.     [source setConnected:YES];
  226.     if(![source isConnected])
  227.     {
  228.         ErrorMsg(errorTitle, "Can't connect to source %s.", [source key]);
  229.         continue;
  230.     }
  231.     if(!(database = [source valueForStringKey:":database-name"]))
  232.     {
  233.         ErrorMsg(errorTitle, "Bad source %s.", [source key]);
  234.         continue;
  235.     }
  236.     
  237.     // Lock transaction to prevent conflicts between threads.
  238.     [Wais lockTransaction];
  239.  
  240.     // Create request message.
  241.     request_buffer_length = [source bufferLength];
  242.     if(!generate_search_apdu(request + HEADER_LENGTH,
  243.         &request_buffer_length, [self keywords], database, Doco, limit))
  244.     {
  245.         [Wais unlockTransaction];
  246.         ErrorMsg(errorTitle,
  247.             "Buffer overflow: request too large for source %s.",
  248.         [source key]);
  249.         continue;
  250.     }
  251.     
  252.     // Send request message.
  253.     if(!interpret_message(request, MAX_MESSAGE_LEN - request_buffer_length, 
  254.         response, MAX_MESSAGE_LEN, [source connection], false))
  255.     {
  256.         [Wais unlockTransaction];
  257.         ErrorMsg(errorTitle, 
  258.             "Warning: no information returned from source %s.",
  259.         [source key]);
  260.         continue;
  261.     }
  262.     
  263.     // Decode search response.
  264.     // Transaction is done; unlock.
  265.     if(!readSearchResponseAPDU(&interp_response, response + HEADER_LENGTH))
  266.     {
  267.         [Wais unlockTransaction];
  268.         ErrorMsg(errorTitle, 
  269.             "Source %s returned bad search response.",
  270.         [source key]);
  271.         continue;
  272.     }
  273.     [Wais unlockTransaction];
  274.     if(interp_response
  275.         && (WAISSearchResponse *)interp_response
  276.             ->DatabaseDiagnosticRecords 
  277.         && (diag = ((WAISSearchResponse *)interp_response
  278.             ->DatabaseDiagnosticRecords)->Diagnostics)
  279.         )
  280.         for(i=0; diag[i]; i++) if(diag[i]->ADDINFO)
  281.             ErrorMsg(errorTitle, "Search diagnostics: %s, %s",
  282.             diag[i]->DIAG, diag[i]->ADDINFO);
  283.  
  284.     // Build sorted result list with info in DocHeaders of search response.
  285.     nretrieve = interp_response->NumberOfRecordsReturned;
  286.     if(nretrieve==0
  287.         || !interp_response->DatabaseDiagnosticRecords
  288.         || !((WAISSearchResponse*)interp_response
  289.             ->DatabaseDiagnosticRecords)->DocHeaders)
  290.             continue;
  291.     for(d=0; d<nretrieve; d++) if(header=((WAISSearchResponse*)
  292.         interp_response->DatabaseDiagnosticRecords)->DocHeaders[d])
  293.     {
  294.         // Create Wais document.
  295.         //!!! Note that this will mask out any previous doc by this name.
  296.         //!!! Should select type based on prefs.
  297.         if(header->Types) theType = header->Types[0];
  298.         else theType = NULL;
  299.         if(!(doc = [[WaisDocument alloc] initKey:NULL])
  300.             || ![doc setFromSource:source]
  301.         || ![doc setWaisDocIDFromAny:header->DocumentID])
  302.         {
  303.             ErrorMsg(errorTitle, "Can't form document for headline %s.",
  304.             header->Headline);
  305.         continue;
  306.         }
  307.         [doc insertStringKey:":headline" value:header->Headline];
  308.         [doc insertStringKey:":type" value:theType];
  309.         if(![doc setKeyFromInfo])
  310.         {
  311.             ErrorMsg(errorTitle, "Can't form document for headline %s.",
  312.             header->Headline);
  313.         continue;
  314.         }
  315.  
  316.         // Place in result list...keep raw scores descending.
  317.         nresult = [resultList count];
  318.         for(r=0; r<nresult; r++) if(!(score=[[resultList objectAt:r]
  319.         valueForStringKey:":score"]) || header->Score>atol(score))
  320.         break;
  321.         [resultList insertObject:doc at:r];
  322.         
  323.         // Fill in other info fields from header.
  324.         // We ignore header->Source.
  325.         if(header->OriginCity)
  326.             [doc insertStringKey:":origin-city" value:header->OriginCity];
  327.         sprintf(buf, "%d", header->Score);
  328.         [doc insertStringKey:":score" value:buf];
  329.         sprintf(buf, "%ld", header->Lines);
  330.         [doc insertStringKey:":number-of-lines" value:buf];
  331.         sprintf(buf, "%ld", header->DocumentLength);
  332.         [doc insertStringKey:":number-of-bytes" value:buf];
  333.         sprintf(buf, "%ld", header->BestMatch);
  334.         [doc insertStringKey:":best-line" value:buf];
  335.         [doc insertStringKey:":start" value:"-1"];
  336.         [doc insertStringKey:":end" value:"-1"];
  337.     }
  338.     }
  339.     
  340.     // Compute and normalize scores.
  341.     if([resultList count] <= 0)
  342.     {
  343.     ErrorMsg(errorTitle, "Found no documents matching the question.");
  344.     return nil;
  345.     }
  346.     if(![self setScoresFromResults])
  347.     {
  348.         [resultList empty]; [scoreList setNumSlots:0];
  349.     ErrorMsg(errorTitle, "Bad document scores.");
  350.     return nil;
  351.     }
  352.     return resultList;
  353. }
  354.  
  355. - resultList
  356. {
  357.     return resultList;
  358. }
  359.  
  360. - (const char *)keywords
  361. {
  362.     return [self valueForStringKey:":seed-words"];
  363. }
  364.  
  365. - setKeywords:(const char *)theText
  366. {
  367.     [self insertStringKey:":seed-words" value:theText];
  368.     return self;
  369. }
  370.  
  371. - addSource:waisSource
  372. {
  373.     if(![waisSource isKindOf:[WaisSource class]]) return nil;
  374.     return [sourceList addObjectIfAbsent:waisSource];
  375. }
  376.  
  377. - removeSource:waisSource
  378. {
  379.     return [sourceList removeObject:waisSource];
  380. }
  381.  
  382. - clearSources
  383. {
  384.     [sourceList empty];
  385.     return self;
  386. }
  387.  
  388. - sourceList
  389. {
  390.     return sourceList;
  391. }
  392.  
  393. - addRelevantDocument:waisDocument
  394. {
  395.     if(![waisDocument isKindOf:[WaisDocument class]]) return nil;
  396.     return [relevantList addObjectIfAbsent:waisDocument];
  397. }
  398.  
  399. - removeRelevantDocument:waisDocument
  400. {
  401.     return [relevantList removeObject:waisDocument];
  402. }
  403.  
  404. - clearRelevantDocuments
  405. {
  406.     [relevantList empty];
  407.     return self;
  408. }
  409.  
  410. - relevantList
  411. {
  412.     return relevantList;
  413. }
  414.  
  415. + setSearchLimit:(int)maxDocs
  416. {
  417.     if(maxDocs <= 0) return nil;
  418.     globalSearchLimit = maxDocs;
  419.     return self;
  420. }
  421.  
  422. + (int)searchLimit
  423. {
  424.     return globalSearchLimit;
  425. }
  426.  
  427. - setSearchLimit:(int)maxDocs
  428. {
  429.     if(maxDocs <= 0) return nil;
  430.     searchLimit = maxDocs;
  431.     return self;
  432. }
  433.  
  434. - (int)searchLimit
  435. {
  436.     return searchLimit;
  437. }
  438.  
  439. - (short)readWaisStruct:(const char *)structName
  440.     forElement:(const char *)elementName
  441.     fromFile:(FILE *)file
  442.     withDecoder:(WaisDecoder)theDecoder
  443. {
  444.     id subObj, inList, origObj;
  445.     short check_result;
  446.     
  447.     // Check if need additional subobject in a list to capture new data.
  448.     subObj = nil;
  449.     if(0 == strcmp(elementName, ":relevant-documents"))
  450.         { inList = relevantList; subObj = [[WaisDocument alloc] initKey:NULL];}
  451.     else if(0 == strcmp(elementName, ":result-documents"))
  452.         { inList = resultList; subObj = [[WaisDocument alloc] initKey:NULL];}
  453.     else if(0 == strcmp(elementName, ":sources"))
  454.         { inList = sourceList; subObj = [[WaisSource alloc] initKey:NULL];}
  455.     else
  456.     {
  457.         // If not, and done reading full question record, update score list.
  458.         check_result = [super readWaisStruct:structName
  459.         forElement:elementName fromFile:file withDecoder:theDecoder];
  460.     if(check_result == FALSE) return check_result;
  461.     if(![self setScoresFromResults]) return FALSE;
  462.     return check_result;
  463.     }
  464.     
  465.     // Read into subobject.
  466.     if(!subObj || !inList) return FALSE;
  467.     check_result = [subObj readWaisStruct:structName
  468.     forElement:elementName fromFile:file withDecoder:theDecoder];
  469.     if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
  470.     { [subObj free]; return check_result; }
  471.     if(inList == sourceList)
  472.     {
  473.         // Convert source-id's into sources.
  474.         origObj = subObj;
  475.     if(!(subObj = [WaisSource objectForKey:[origObj
  476.         valueForStringKey:":filename"]]))
  477.     {
  478.         ErrorMsg(errorTitle, "Unknown source %s.",
  479.             [origObj valueForStringKey:":filename"]);
  480.         [origObj free]; return TRUE;
  481.     }
  482.     [origObj free];
  483.     }
  484.     [inList addObject:subObj];
  485.     return TRUE;
  486. }
  487.  
  488. - (short)writeWaisStruct:(const char *)structName
  489.     forElement:(const char *)elementName
  490.     toFile:(FILE *)file
  491.     withDecoder:(WaisDecoder)theDecoder
  492. {
  493.     id subObj;
  494.     short check_result;
  495.     
  496.     // Check if need next subobject in a list to extract data from.
  497.     // The listCounter keeps track of where we are in list.
  498.     subObj = nil;
  499.     if(0 == strcmp(elementName, ":relevant-documents"))
  500.         subObj = [relevantList objectAt:listCounter];
  501.     else if(0 == strcmp(elementName, ":result-documents"))
  502.         subObj = [resultList objectAt:listCounter];
  503.     else if(0 == strcmp(elementName, ":sources"))
  504.         subObj = [sourceList objectAt:listCounter];
  505.     else
  506.     {
  507.         listCounter = 0;
  508.         return [super writeWaisStruct:structName
  509.         forElement:elementName toFile:file withDecoder:theDecoder];
  510.     }
  511.     
  512.     // Write from subobject, incrementing or clearing listCounter as needed.
  513.     if(!subObj) { listCounter = 0; return END_OF_STRUCT_OR_LIST; }
  514.     else listCounter++;
  515.     check_result = [subObj writeWaisStruct:structName
  516.     forElement:elementName toFile:file withDecoder:theDecoder];
  517.     if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
  518.     listCounter = 0;
  519.     return check_result;
  520. }
  521.  
  522. - writeWaisFile
  523. {
  524.     // Fill in missing fields.
  525.     [self insertStringKey:":version" value:WAIS_PROTOCOL_VERSION];
  526.     return [super writeWaisFile];
  527. }
  528.  
  529.  
  530. @end
  531.  
  532.  
  533.  
  534.